package ru.classbase.dbdumper.writer

import org.springframework.stereotype.Component
import ru.classbase.dbdumper.AppProperties
import ru.classbase.dbdumper.model.Column
import ru.classbase.dbdumper.model.DbMetaData
import ru.classbase.dbdumper.model.DumpContext
import ru.classbase.dbdumper.model.OutputFormat
import java.sql.ResultSet
import java.time.format.DateTimeFormatter
import javax.xml.stream.XMLOutputFactory
import javax.xml.stream.XMLStreamWriter


@Component
class XmlWriter(
    private val props: AppProperties,
    private val dateTimeFormatter: DateTimeFormatter,
    private val dateFormatter: DateTimeFormatter,
) : DumpWriter {

    override val supportedFormat =  OutputFormat.XML

    private val xmlFactory = XMLOutputFactory.newInstance()

    override fun write(metaData: DbMetaData, context: DumpContext) {

        val (schema, tableName, columns, rs, writer) = context

        val gen = xmlFactory.createXMLStreamWriter(writer)
        try {
            gen.writeStartDocument(props.outputEncoding.name(), "1.0")
            lf(gen)

            startElem(gen, "Dataset")
            element(gen, "Database", metaData.database)
            element(gen, "Schema", schema)
            element(gen, "Table", tableName)
            startElem(gen, "Rows")

            while (rs.next()) {
                writeRow(rs, gen, columns)
            }

            endElem(gen)
            endElem(gen)
            gen.writeEndDocument()
        } finally {
            gen.flush()
            gen.close()
        }
    }

    private fun writeRow(rs: ResultSet, gen: XMLStreamWriter, columns: List<Column>) {
        startElem(gen, "Row")

        for (i in 1..columns.size) {
            val colName = columns[i - 1].columnName
            startElem(gen, "Col")
            element(gen, "Name", colName)

            when (columns[i - 1].dataType) {
                "integer" -> writeInt(gen, rs, i)
                "date" -> writeDate(gen, rs, i)
                "smallint" -> writeSmallInt(gen, rs, i)
                "timestamp without time zone" -> writeTimestamp(gen, rs, i)
                "boolean" -> writeBoolean(gen, rs, i)
                "character varying" -> writeString(gen, rs, i)
                "numeric" -> writeNumeric(gen, rs, i)
                "bigint" -> writeBigInt(gen, rs, i)
                else -> throw IllegalArgumentException("Тип данных [${columns[i - 1].dataType}]  не поддерживается")
            }
            endElem(gen)

        }
        endElem(gen)
    }

    fun writeString(gen: XMLStreamWriter, rs: ResultSet, colNum: Int) {
        val value = rs.getString(colNum)

        if (rs.wasNull()) {
            emptyVal(gen)
            return
        }

        writeValue(gen, value)
    }

    fun writeBoolean(gen: XMLStreamWriter, rs: ResultSet, colNum: Int) {
        val value = rs.getBoolean(colNum)

        if (rs.wasNull()) {
            emptyVal(gen)
            return
        }

        writeValue(gen, value.toString())
    }

    fun writeSmallInt(gen: XMLStreamWriter, rs: ResultSet, colNum: Int) {
        val value = rs.getShort(colNum)

        if (rs.wasNull()) {
            emptyVal(gen)
            return
        }

        writeValue(gen, value.toString())
    }

    fun writeInt(gen: XMLStreamWriter, rs: ResultSet, colNum: Int) {
        val value = rs.getInt(colNum)

        if (rs.wasNull()) {
            emptyVal(gen)
            return
        }

        writeValue(gen, value.toString())
    }

    fun writeBigInt(gen: XMLStreamWriter, rs: ResultSet, colNum: Int) {
        val value = rs.getLong(colNum)

        if (rs.wasNull()) {
            emptyVal(gen)
            return
        }

        writeValue(gen, value.toString())
    }

    fun writeNumeric(gen: XMLStreamWriter, rs: ResultSet, colNum: Int) {
        val value = rs.getBigDecimal(colNum)

        if (rs.wasNull()) {
            emptyVal(gen)
            return
        }

        writeValue(gen, value.toPlainString())
    }

    fun writeDate(gen: XMLStreamWriter, rs: ResultSet, colNum: Int) {
        val value = rs.getDate(colNum)

        if (rs.wasNull()) {
            emptyVal(gen)
            return
        }

        writeValue(gen, dateFormatter.format(value.toLocalDate()))
    }

    fun writeTimestamp(gen: XMLStreamWriter, rs: ResultSet, colNum: Int) {
        val value = rs.getTimestamp(colNum)

        if (rs.wasNull()) {
            emptyVal(gen)
            return
        }

        writeValue(gen, dateTimeFormatter.format(value.toLocalDateTime()))
    }

    fun element(gen: XMLStreamWriter, name: String, value: String) {
        gen.writeStartElement(name)
        gen.writeCharacters(value)
        gen.writeEndElement()
        lf(gen)
    }

    fun lf(gen: XMLStreamWriter) {
        gen.writeCharacters("\n")
    }

    private fun startElem(gen: XMLStreamWriter, name: String) {
        gen.writeStartElement(name)
        lf(gen)
    }

    private fun endElem(gen: XMLStreamWriter) {
        gen.writeEndElement()
        lf(gen)
    }

    private fun emptyVal(gen: XMLStreamWriter) {
        gen.writeEmptyElement("Val")
        lf(gen)
    }

    private fun writeValue(gen: XMLStreamWriter, value: String) {
        gen.writeStartElement("Val")
        gen.writeCharacters(value)
        gen.writeEndElement()
    }

}
